package parsing.yago;

import java.util.TreeMap;

import core.objects.Book;
import core.objects.Category;
import core.objects.City;
import core.objects.Country;
import core.objects.Publisher;
import core.objects.Author;


public class LabelsParser extends YagoFileParser {

	private static final String LABELS = "yagoLabels.tsv";	
	private static final int ESTIMATED_LINES_NUM = 16500000;

	private static final String IS_PREFERRED_MEANING_OF = "isPreferredMeaningOf";

	/* This TreeMap will hold the preferred meaning of books, to help
	 * detect two books with the same meaning (i.e. they're the same book
	 * and should be one object) */
	private TreeMap<String, Book> preferredMeaningBooks;
	
	/* Do the same for other objects: */
	private TreeMap<String, Country> preferredMeaningCountries;
	private TreeMap<String, Publisher> preferredMeaningPublishers;
	private TreeMap<String, Category> preferredMeaningCategories;
	private TreeMap<String, Author> preferredMeaningWriters;
	private TreeMap<String, City> preferredMeaningCities;
	
	protected LabelsParser(String directory) {
		super(directory);
		preferredMeaningBooks = new TreeMap<String, Book>();
		preferredMeaningCountries = new TreeMap<String, Country>();
		preferredMeaningPublishers = new TreeMap<String, Publisher>();
		preferredMeaningCategories = new TreeMap<String, Category>();
		preferredMeaningWriters = new TreeMap<String, Author>();
		preferredMeaningCities = new TreeMap<String, City>();
	}

	@Override
	protected String getFileName() {
		return LABELS;
	}
	
	@Override
	protected int getEstimatedFileLinesNumber() {
		return ESTIMATED_LINES_NUM;
	}

	@Override
	protected void handleLineParsing(String[] line) {
	
		if (line.length != 4)
			return;

		/* line is <weird-id> <object> <isPreferredMeaningOf/rdfs:* /...> somevalue@eng */
		
		if (line[2].equals(IS_PREFERRED_MEANING_OF)) {
			
			String prefMeaning = getPreferredMeaning(line[3]);
			
			Country country = ParsingEnvironment.getCountries().get(line[1]);
			if (country != null) {
				Country prefCountry = preferredMeaningCountries.get(prefMeaning);
				if (prefCountry == null) {
					//country has not been found with another preferred meaning:
					preferredMeaningCountries.put(prefMeaning, country);
				} else {
					//two countries of the same preferred meaning were found:
					handleCountriesPrefMeaningCollision(prefCountry);
				}
			} else {
				
				Publisher publisher = ParsingEnvironment.getPublishers().get(line[1]);
				if (publisher != null) {
					Publisher prefPublisher = preferredMeaningPublishers.get(prefMeaning);
					if (prefPublisher == null) {
						//country has not been found with another preferred meaning:
						preferredMeaningPublishers.put(prefMeaning, publisher);
					} else {
						//two countries of the same preferred meaning were found:
						handlePublishersPrefMeaningCollision(prefPublisher);
					}
				} else {
					Category category = ParsingEnvironment.getCategories().get(line[1]);
					if (category != null) {
						Category prefCategory = preferredMeaningCategories.get(prefMeaning);
						if (prefCategory == null) {
							//country has not been found with another preferred meaning:
							preferredMeaningCategories.put(prefMeaning, category);
						} else {
							//two countries of the same preferred meaning were found:
							handleCategoriesPrefMeaningCollision(prefCategory);
						}
					} else {
						Author writer = ParsingEnvironment.getWriters().get(line[1]);
						if (writer != null) {
							Author prefWriter = preferredMeaningWriters.get(prefMeaning);
							if (prefWriter == null) {
								//country has not been found with another preferred meaning:
								preferredMeaningWriters.put(prefMeaning, writer);
							} else {
								//two countries of the same preferred meaning were found:
								handleWritersPrefMeaningCollision(prefWriter);
							}
						} else {
							City city = ParsingEnvironment.getCities().get(line[1]);
							if (city != null) {
								City prefCity = preferredMeaningCities.get(prefMeaning);
								if (prefCity == null) {
									//country has not been found with another preferred meaning:
									preferredMeaningCities.put(prefMeaning, city);
								} else {
									//two countries of the same preferred meaning were found:
									handleCitiesPrefMeaningCollision(prefCity);
								}
							} else {
			
								Book book = ParsingEnvironment.getBooks().get(line[1]);
								if (book != null) {
									synchronized(book) {
										Book prefBook = preferredMeaningBooks.get(prefMeaning);
										if (prefBook == null) {
											//book has not been found with another preferred meaning:
											preferredMeaningBooks.put(prefMeaning, book);
										} else {
											//two books of the same preferred meaning were found:
											mergeBooks(book, prefBook, prefMeaning);
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}

	@Override
	protected boolean isLineFromFileOkay(String[] line) {
		//too many possibilities in this file:
		return true;
	}
	
	/* removes @lang at the end of string (if exists) */
	private String getPreferredMeaning(String part) {
		int indx = part.indexOf('@');
		if (indx >= 0) {
			part = part.substring(0, indx);
		}
		return part.replace("\"", "").trim();
	}
	
	private void mergeBooks(Book book, Book prefBook, String prefMeaning) {
				
		Book newBook = new Book(/*ID doesn't matter*/ 0, prefMeaning);
		
		/* YAGO is really messed up if two books of the same preferred meaning
		 * have two different values to attributes (could be one has and the
		 * other doesn't, but not both have values and are different) !! */
		
		if (book.getISBN() == null) {
			newBook.setISBN(prefBook.getISBN());
		} else {
			newBook.setISBN(book.getISBN());
		}
		
		if (book.getNumOfPages() == 0) {
			newBook.setNumOfPages(prefBook.getNumOfPages());
		} else {
			newBook.setNumOfPages(book.getNumOfPages());
		}

		if (book.getReleaseYear() == null) {
			newBook.setReleaseYear(prefBook.getReleaseYear());
		} else {
			newBook.setReleaseYear(book.getReleaseYear());
		}
		
		if (book.getWikipediaURL() == null) {
			newBook.setWikipediaURL(prefBook.getWikipediaURL());
		} else {
			newBook.setWikipediaURL(book.getWikipediaURL());
		}
				
		/* writers, publishers and categories could be different - merge!: */

		Book[] books = { book, prefBook };

		for (Book b : books) {
			if (b.getCategories() != null) {
				for (Category c : b.getCategories()) {
					newBook.addCategory(c);
				}
			}			
		}
		
		for (Book b : books) {
			if (b.getAuthors() != null) {
				for (Author w : b.getAuthors()) {
					newBook.addAuthor(w);
				}
			}			
		}

		for (Book b : books) {
			if (b.getPublishers() != null) {
				for (Publisher p : b.getPublishers()) {
					newBook.addPublisher(p);
				}
			}			
		}
		
		/* remove both books from parsing environment and add the new book instead: */
		ParsingEnvironment.getBooks().remove(book.getName());
		ParsingEnvironment.getBooks().remove(prefBook.getName());
		preferredMeaningBooks.remove(prefBook.getName());
		ParsingEnvironment.getBooks().put(prefMeaning, newBook);
		preferredMeaningBooks.put(prefMeaning, newBook);
		
	}
	
	private void handleCountriesPrefMeaningCollision(Country country) {
		//country is a pretty shallow object, so just remove the duplicate:
		ParsingEnvironment.getCountries().remove(country.getName());
	}
	
	private void handlePublishersPrefMeaningCollision(Publisher publisher) {
		//publisher is a pretty shallow object, so just remove the duplicate:
		ParsingEnvironment.getPublishers().remove(publisher.getName());
	}
	
	private void handleCategoriesPrefMeaningCollision(Category category) {
		//category is a pretty shallow object, so just remove the duplicate:
		ParsingEnvironment.getCategories().remove(category.getName());
	}
	
	private void handleWritersPrefMeaningCollision(Author writer) {
		//writer is a pretty shallow object, so just remove the duplicate:
		ParsingEnvironment.getWriters().remove(writer.getName());
	}

	private void handleCitiesPrefMeaningCollision(City city) {
		//city is a pretty shallow object, so just remove the duplicate:
		ParsingEnvironment.getCities().remove(city.getName());
	}

}
